import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react';

export type BasicTarget<T = HTMLElement> =
  | (() => T | null)
  | T
  | null
  | MutableRefObject<T | null | undefined>;
export interface Options {
  type?: 'js' | 'css' | 'img';
  media?: HTMLLinkElement['media'];
  async?: boolean;
  target?: BasicTarget;
  name?: string;
}
type TargetElement = HTMLElement | Element | Document | Window;
export function getTargetElement(
  target?: BasicTarget<TargetElement>,
  defaultElement?: TargetElement
): TargetElement | undefined | null {
  if (!target) {
    return defaultElement;
  }

  let targetElement: TargetElement | undefined | null;

  if (typeof target === 'function') {
    targetElement = target();
  } else if ('current' in target) {
    targetElement = target.current;
  } else {
    targetElement = target;
  }

  return targetElement;
}
export type Status = 'unset' | 'loading' | 'ready' | 'error';

export type Action = {
  toggle: () => void;
  load: () => void;
  unload: () => void;
};

export type ExternalElement = HTMLScriptElement | HTMLLinkElement | HTMLImageElement;
/**
 *
 * @param path
 * @param options
 * @returns
 */
export default function useExternal(path: string, options?: Options): [Status, Action] {
  const isPath = typeof path === 'string' && path !== '';

  const [status, setStatus] = useState<Status>(isPath ? 'loading' : 'unset');

  const [active, setActive] = useState(isPath);

  const ref = useRef<ExternalElement>();

  useEffect(() => {
    ref.current?.remove();

    if (!isPath || !active) {
      setStatus('unset');
      ref.current = undefined;
      return;
    }

    setStatus('loading');
    // Create external element
    const pathname = path.replace(/[|#].*$/, '');
    if (options?.type === 'css' || /(^css!|\.css$)/.test(pathname)) {
      // css
      let current: HTMLLinkElement | null = null;
      let needInsert = true;
      if (options?.name) {
        const nameEl = document.querySelector<HTMLLinkElement>(`link[data-name="${options.name}"]`);
        if (nameEl) {
          needInsert = false;
          current = nameEl;
        }
      }
      if (current) {
        if (current.attributes.getNamedItem('href')?.nodeValue === path) {
          setStatus('ready');
          return;
        }
      } else {
        current = document.createElement('link');
      }
      ref.current = current;
      ref.current.rel = 'stylesheet';
      ref.current.href = path;
      ref.current.media = options?.media || 'all';
      // IE9+
      const isLegacyIECss = 'hideFocus' in ref.current;
      // use preload in IE Edge (to detect load errors)
      if (isLegacyIECss && ref.current.relList) {
        ref.current.rel = 'preload';
        ref.current.as = 'style';
      }
      ref.current.setAttribute('data-status', 'loading');
      if (options?.name) {
        ref.current.setAttribute('data-name', `${options.name}`);
      }
      if (needInsert) {
        document.body.insertBefore(ref.current, document.body.firstChild);
      }
    } else if (options?.type === 'js' || /(^js!|\.js$)/.test(pathname)) {
      // javascript
      ref.current = document.createElement('script');
      ref.current.src = path;
      ref.current.async = options?.async === undefined ? true : options?.async;
      ref.current.setAttribute('data-status', 'loading');
      document.body.appendChild(ref.current);
    } else if (options?.type === 'img' || /(^img!|\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {
      // image
      ref.current = document.createElement('img');
      ref.current.src = path;
      ref.current.setAttribute('data-status', 'loading');
      // append to wrapper
      const wrapper = (getTargetElement(options?.target) as HTMLElement) || document.body;
      if (wrapper) {
        wrapper.appendChild(ref.current);
      }
    } else {
      // do nothing
      console.error(
        "Cannot infer the type of external resource, and please provide a type ('js' | 'css' | 'img'). " +
          'Refer to the https://ahooks.js.org/hooks/dom/use-external/#options'
      );
    }

    if (!ref.current) return;

    // Bind setAttribute Event
    const setAttributeFromEvent = (event: Event) => {
      ref.current?.setAttribute('data-status', event.type === 'load' ? 'ready' : 'error');
    };
    ref.current.addEventListener('load', setAttributeFromEvent);
    ref.current.addEventListener('error', setAttributeFromEvent);
    const setStateFromEvent = (event: Event) => {
      setStatus(event.type === 'load' ? 'ready' : 'error');
    };
    ref.current.addEventListener('load', setStateFromEvent);
    ref.current.addEventListener('error', setStateFromEvent);
    return () => {
      ref.current?.removeEventListener('load', setStateFromEvent);
      ref.current?.removeEventListener('error', setStateFromEvent);
    };
  }, [path, active]);

  const action = useMemo(() => {
    const unload = () => setActive(false);
    const load = () => setActive(true);
    const toggle = () => setActive((value) => !value);
    return { toggle, load, unload };
  }, [setActive]);

  return [status, action];
}
